//
//  TXNetworkManager.swift
//  Here_Swift
//
//  Created by 童煊 on 2018/1/9.
//  Copyright © 2018年 童煊. All rights reserved.
//

import UIKit
import Alamofire

/** 请求类型*/
enum NetworkRequestType:String {
    case GET, POST, PATCH, DELETE, PUT
}

/** 响应闭包*/
typealias TXResponseClosure = (_ data:Any?, _ error:NSError?) -> Void
/** 失败响应闭包*/
typealias TXFailResponseClosure = (_ data:Any?, _ error:NSError?, _ fRModel:TXFailRequestModel?) -> Void


class TXNetworkManager: NSObject {
    
    /** 基础url*/
    public static let baseUrl:String = "http://10.0.0.31:8080/nhere/"
    
    /** 网络管理单例*/
    static let networkManager = TXNetworkManager()
    
    /** 鉴权认证*/
    var oauthCredential : OAuthCredential = OAuthCredential.pullOAuth2Token()
    
    /** 网络请求头部*/
    var headers:HTTPHeaders = [:]
    
    /** 网络请求会话管理*/
    let sessionManager:SessionManager = SessionManager.init()
    
    /** 请求缓存，最大限制50个，超出则清空缓存*/
    var dataTaskCache = TXNetworkTaskCache()
    
    
    ///重载 私有化init() 方法
    private override init() {
        super.init()
        //创建缓存
        self.dataTaskCache.addObserver(self, forKeyPath: "cache", options: NSKeyValueObservingOptions.new, context: nil)
        //添加请求token头部
        self.sessionManager.adapter = AccessTokenAdapter(accessToken: OAuthCredential.pullOAuth2Token().access_token!)
        //OAuth2验证刷新
        self.sessionManager.retrier = OAuth2Handler()
    }
    
    ///类方法返回单例
    class func shareManager() -> TXNetworkManager{
        return networkManager
    }
    
    /// 网络请求简易方法 自动保存失败请求
    ///
    /// - Parameter path:请求路径
    /// - Parameter params:请求参数
    /// - Parameter requestType:请求类型
    /// - Parameter successClosure:请求成功回调
    /// - Parameter failureClosure:请求失败回调
    public func networkRequest(
        path:String,
        params:Dictionary<String, Any>?,
        requestType:NetworkRequestType,
        successClosure: @escaping TXResponseClosure,
        failureClosure: @escaping TXFailResponseClosure)
        -> Void
    {
        
        return self.networkRequest(
            path: path,
            params: params,
            requestType: requestType,
            progressClosure: { (progress) in
                
        },
            successClosure: successClosure,
            failureClosure: failureClosure,
            autoSaveFailRequest: true)
    }
    
    ///网络请求简易方法 可选是否保存失败请求
    ///
    /// - Parameter path:请求路径
    /// - Parameter params:请求参数
    /// - Parameter requestType:请求类型
    /// - Parameter successClosure:请求成功回调
    /// - Parameter failureClosure:请求失败回调
    /// - Parameter autoSaveFailRequest:是否保存失败请求
    public func networkRequest(
        path:String,
        params:Dictionary<String, Any>?,
        requestType:NetworkRequestType,
        successClosure: @escaping TXResponseClosure,
        failureClosure:@escaping TXFailResponseClosure,
        autoSaveFailRequest:Bool)
        -> Void
    {
        return self.networkRequest(
            path: path,
            params: params,
            requestType: requestType,
            progressClosure: { (progress) in
                
        },
            successClosure: successClosure,
            failureClosure: failureClosure,
            autoSaveFailRequest: autoSaveFailRequest)
    }
    
    ///网络请求主方法 可选是否保存失败请求
    ///
    /// - Parameter path:请求路径
    /// - Parameter params:请求参数
    /// - Parameter requestType:请求类型
    /// - Parameter progressClosure:请求进度回调
    /// - Parameter successClosure:请求成功回调
    /// - Parameter failureClosure:请求失败回调
    /// - Parameter autoSaveFailRequest:是否保存失败请求
    public func networkRequest(
        path:String,
        params:Dictionary<String, Any>?,
        requestType:NetworkRequestType,
        progressClosure:@escaping (Progress)->Void,
        successClosure: @escaping TXResponseClosure,
        failureClosure:@escaping TXFailResponseClosure,
        autoSaveFailRequest:Bool)
        -> Void
    {
        var newParams:Dictionary<String, Any>
        if params==nil {
            newParams = [:]
        }else{
            newParams = params!
        }
        
        //*若没有以上字符 则返回原字符串
        
        //        path = [path stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];
        print("\n==========Network Request=========\n path:\(path) \n type:\(requestType.rawValue) \n params:\(newParams)")
        
        //拉取证书
        self.oauthCredential = OAuthCredential.pullOAuth2Token()
        if self.oauthCredential.access_token?.count != 0{
            self.headers = OAuth2Handler.setAuthorizationHeaderFieldWithCredential(credential: self.oauthCredential)
        }else{
            //若拉取失败则不作处理，通过后台返回情况决定刷新token或是退出登录
        }
        
        
        let dataRequest : DataRequest = self.sessionManager.request(URL.init(string: TXNetworkManager.baseUrl + path)!, method: HTTPMethod(rawValue: requestType.rawValue)!, parameters:newParams, encoding: JSONEncoding.default, headers: headers).downloadProgress(closure: { (progress) in
            progressClosure(progress)
        }).validate(statusCode: 200..<300).responseJSON { (responesData) in
            //            print("Request: \(responesData.request)")
            //            print("Response: \(responesData.response)")
            //            print("Error: \(responesData.error)")
            
            switch responesData.result{
            case .success:
                if let jsonResult = responesData.result.value{
                    
                    //                    print("jsonResult:\(jsonResult)")
                    successClosure(jsonResult, NSError())
                }
            case .failure(let error):
                print(error)
                //错误
                
                let fRModel = TXFailRequestModel.failRequestModelWith(path: path, params: params, requestType: requestType, successClosure: successClosure)
                if autoSaveFailRequest {
                    TXFRCDManager.sharedManager().sendFailRequestWithModel(model: fRModel, errorInfo: responesData.error as Any)
                    failureClosure(responesData, responesData.error as NSError?, fRModel)
                }else{
                    failureClosure(responesData, responesData.error as NSError?, fRModel)
                    
                }
                
            }
        }
        
        
        self.dataTaskCache.mutableArrayValue(forKey: "cache").add(dataRequest)
    }
    
    /**
     取消没有完成的任务，并清空请求缓存
     */
    public func stopUnfinishedTask() -> Void{
        for (index, object) in self.dataTaskCache.cache.enumerated().reversed() {
            (object as! DataRequest).task?.cancel()
            self.dataTaskCache.cache.remove(at: index)
        }
    }
    
    
    /**
     null转nil cfumber转String
     */
    class func setNullToNil_Str(info:Dictionary<String, Any>) -> Dictionary<String, Any>{
        var _info = info
        
        for (key, object) in _info {
            if object is NSNull{
                _info.updateValue("", forKey: key)
            }
            if object is NSNumber{
                _info.updateValue(String.init(format: "%@", object as! CVarArg), forKey: key)
            }
        }
        return _info
    }
    /**
     KVO回收请求缓存
     */
    override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
        for (index, object) in self.dataTaskCache.cache.enumerated().reversed() {
            if (object as? DataRequest)?.task?.state == .canceling || (object as! DataRequest).task?.state == .completed{
                self.dataTaskCache.cache.remove(at: index)
            }
        }
        if self.dataTaskCache.cache.count > TXNetworkTaskCache.TXNetWorkCacheMaxCapacity {
            self.dataTaskCache.cache.removeAll()
            print("dataTaskCache MAX delete:%@",self.dataTaskCache.cache)
        }
    }
}

/** 请求适配器 添加请求头*/
class AccessTokenAdapter: RequestAdapter {
    private let accessToken:String
    
    init(accessToken:String) {
        self.accessToken = accessToken
    }
    func adapt(_ urlRequest: URLRequest) throws -> URLRequest {
        var urlRequest = urlRequest
        if (urlRequest.url?.relativeString.hasPrefix(TXNetworkManager.baseUrl))! {
            urlRequest.setValue("Bearer" + accessToken, forHTTPHeaderField: "Authorization")
        }
        return urlRequest
    }
}

/** 请求重联 刷新token*/
class OAuth2Handler: RequestRetrier {
    
    static let clientId = "here_official_app"
    static let clientSecret = "86fa83a5-7442-4779-bf78-cd0d5b767fdd"
    static let pathToGetOAuth2Token = TXNetworkManager.baseUrl + "oauth/token"
    
    static let clientData : NSData = "\(OAuth2Handler.clientId):\(OAuth2Handler.clientSecret)".data(using: String.Encoding.utf8)! as NSData
    static let client64String = OAuth2Handler.clientData.base64EncodedString(options: NSData.Base64EncodingOptions.init(rawValue: 0))
    
    
    public func should(_ manager: SessionManager, retry request: Request, with error: Error, completion: @escaping RequestRetryCompletion) {
        if let response = request.task?.response as? HTTPURLResponse, response.statusCode == 401 {
            self.refreshOAuthToken(success: { (data, error) in
                //刷新成功 重新请求
                manager.request(request as! URLRequestConvertible)
            }, failure: { (dara, error, fRModel) in
                //刷新失败
                print("刷新失败,跳转至登录界面")
            })
        }else{
            print("应跳转至登录界面")
        }
    }
    
    /**
     登录获取token
     */
    public func getOAuthToken(
        username:String,
        password:String,
        success:TXResponseClosure,
        failure:TXFailResponseClosure)
        ->Void{
            let params = ["grant_type":"password","scope":"user_all","username":username,"password":password]
            let httpHeader : HTTPHeaders = ["Authorization":"Basic \(OAuth2Handler.client64String)"]
            
            print("\n==========Network Request=========\n  params:\(params)")
            
            Alamofire.request(URL.init(string: OAuth2Handler.pathToGetOAuth2Token)!, method: .post, parameters: params, encoding: URLEncoding.default, headers: httpHeader).validate(statusCode: 200..<300).responseJSON { (responesData) in
                switch responesData.result{
                case .success(let JsonResult):
                    print("解析数据:",JsonResult)
                    if JsonResult is Dictionary<String, Any>{
                        var replyDic = TXNetworkManager.setNullToNil_Str(info: JsonResult as! Dictionary<String, Any>)
                        //生成认证
                        let credential = OAuthCredential.init(access_token: replyDic["access_token"] as! String, refresh_token: replyDic["refresh_token"] as! String, scope: replyDic["scope"] as! String, token_type: replyDic["token_type"] as! String, expires_in: replyDic["expires_in"] as! String)
                        //保存认证
                        credential.saveCredential()
                    }
                case .failure(let error):
                    print(error)
                }
            }
    }
    
    /**
     刷新token
     */
    public func refreshOAuthToken(success:TXResponseClosure, failure:TXFailResponseClosure) -> Void {
        let credential = OAuthCredential.pullOAuth2Token()
        let params = ["grant_type":"refresh_token","refresh_token":(credential.refresh_token != nil ? credential.refresh_token : "")]
        let httpHeader : HTTPHeaders = ["Authorization":"Basic \(OAuth2Handler.client64String)"]
        
        print("\n==========Network Request=========\n  params:\(params)")
        
        Alamofire.request(URL.init(string: OAuth2Handler.pathToGetOAuth2Token)!, method: .post, parameters: params, encoding: JSONEncoding.default, headers: httpHeader).validate(statusCode: 200..<300).responseJSON { (responesData) in
            
            switch responesData.result{
            case .success(let JsonResult):
                print("解析数据:",JsonResult)
                if JsonResult is Dictionary<String, Any>{
                    var replyDic = TXNetworkManager.setNullToNil_Str(info: JsonResult as! Dictionary<String, Any>)
                    //生成认证
                    let credential = OAuthCredential.init(access_token: replyDic["access_token"] as! String, refresh_token: replyDic["refresh_token"] as! String, scope: replyDic["scope"] as! String, token_type: replyDic["token_type"] as! String, expires_in: replyDic["expires_in"] as! String)
                    //保存认证
                    credential.saveCredential()
                }
                
            case .failure(let error):
                print(error)
            }
        }
    }
    
    /**
     设置请求头
     */
    public class func setAuthorizationHeaderFieldWithCredential(credential:OAuthCredential) -> HTTPHeaders{
        if credential.token_type!.compare("Bearer") == .orderedSame {
            let httpHeader = ["Authorization" : "Bearer \(credential.access_token!)"]
            return httpHeader
        }
        return [:]
    }
}

/** OAuth认证*/
class OAuthCredential: NSObject {
    
    var access_token : String?
    var refresh_token : String?
    var scope : String?
    var token_type : String?
    var expires_in : String?
    
    /**
     新建OAuth认证
     */
    public convenience init(
        access_token:String?,
        refresh_token:String?,
        scope:String?,
        token_type:String?,
        expires_in:String?) {
        self.init()
        self.access_token = access_token
        self.refresh_token = refresh_token
        self.scope = scope
        self.token_type = token_type
        self.expires_in = expires_in
    }
    
    /**
     保存OAuth认证
     */
    public func saveCredential() {
        UserDefaults.OAuth2TokenInfo.set(value: self.access_token, forKey: .access_token)
        UserDefaults.OAuth2TokenInfo.set(value: self.refresh_token, forKey: .refresh_token)
        UserDefaults.OAuth2TokenInfo.set(value: self.scope, forKey: .scope)
        UserDefaults.OAuth2TokenInfo.set(value: self.token_type, forKey: .token_type)
        UserDefaults.OAuth2TokenInfo.set(value: self.expires_in, forKey: .expires_in)
    }
    
    /**
     取出认证
     */
    public class func pullOAuth2Token() -> OAuthCredential{
        return OAuthCredential.init(access_token: UserDefaults.OAuth2TokenInfo.string(forKey: .access_token), refresh_token: UserDefaults.OAuth2TokenInfo.string(forKey: .refresh_token), scope: UserDefaults.OAuth2TokenInfo.string(forKey: .scope), token_type: UserDefaults.OAuth2TokenInfo.string(forKey: .token_type), expires_in: UserDefaults.OAuth2TokenInfo.string(forKey: .expires_in))
    }
}


/** UserDefualt扩展*/
protocol UserDefaultsSettable {
    associatedtype defaultKeys: RawRepresentable
}

extension UserDefaultsSettable where defaultKeys.RawValue==String {
    static func set(value: String?, forKey key: defaultKeys) {
        let aKey = key.rawValue
        UserDefaults.standard.set(value, forKey: aKey)
    }
    static func string(forKey key: defaultKeys) -> String? {
        let aKey = key.rawValue
        return UserDefaults.standard.string(forKey: aKey)
    }
}

extension UserDefaults{
    struct OAuth2TokenInfo:UserDefaultsSettable {
        enum defaultKeys : String {
            case access_token
            case refresh_token
            case scope
            case token_type
            case expires_in
        }
    }
}

